use std::f64::consts::PI;

use glam::{dvec2, DVec2, DVec3};

use crate::{Ray, HitRecord, Hitable, MaterialRef, AABB, Interval};

#[derive(Clone)]
pub struct Sphere {
    pub center: DVec3,
    pub radius: f64,
    pub material: MaterialRef,
    pub aabb: AABB,
}

impl Sphere {
    pub fn new(center: DVec3, radius: f64, material: MaterialRef) -> Self {
        Self {
            center,
            radius,
            material,
            aabb: AABB::new(
                Interval::new(center.x - radius, center.x + radius),
                Interval::new(center.y - radius, center.y + radius),
                Interval::new(center.z - radius, center.z + radius),
            ),
        }
    }

    fn get_sphere_uv(p: &DVec3) -> DVec2 {
        let theta = (-p.y).acos();
        let phi = (-p.z).atan2(p.x) + PI;

        dvec2(
            phi / (2.0 * PI), 
            theta / PI
        )
    }
}

impl Hitable for Sphere {
    fn hit(&self, ray: &Ray, interval: Interval) -> Option<HitRecord> {
        let oc = self.center - ray.orig;
        let a = ray.dir.length_squared();
        let h = ray.dir.dot(oc);
        let c = oc.length_squared() - self.radius * self.radius;

        // discriminant 
        let discriminant = h * h - a * c;

        if discriminant < 0.0 { return None }

        let sqrtd = discriminant.sqrt();

        let mut t = (h - sqrtd) / a;
        if t <= interval.min || interval.max <= t {
            t = (h + sqrtd) / a;
            if t <= interval.min || interval.max <= t {
                return None;
            }
        }

        let p = ray.at(t);
        let n = (p - self.center) / self.radius;

        let uv = Self::get_sphere_uv(&n);

        let mut hit = HitRecord::new(t, p, self.material.clone());
        hit.set_face_normal(&ray.dir, &n);
        hit.uv = uv;

        Some(hit)
    }

    fn bounding_box(&self) -> &AABB {
        &self.aabb
    }
}
